#!/usr/bin/env python
# coding=utf-8

import json
import logging
import os
import platform
import re
import socket
import threading
import time
from datetime import datetime, timedelta
from subprocess import Popen, PIPE
import schedule
import ConfigParser
import distro
import psutil
import requests
from qingcloud.iaas import APIConnection


with open('config/agent_query.json', 'r') as fp:
    json_data = json.load(fp)

asset_info_interval = json_data['asset_info_interval']
agg_sys_interval = json_data['agg_sys_interval']
server_ip = json_data['server_ip']
AGENT_VERSION = json_data['AGENT_VERSION']
headers = json_data['headers']

with open('config/qy_query.json', 'r') as fp:
    json_data = json.load(fp)
qy_param = {unicode(k).encode("utf-8"): unicode(v).encode("utf-8") for k, v in json_data.iteritems()}


def log(log_name, path=None):
    logging.basicConfig(level=logging.INFO, format='%(asctime)s %(levelname)s %(message)s',
                        datefmt='%Y%m%d %H:%M:%S', filename=path + log_name, filemode='ab+')
    return logging.basicConfig


LOG_DIR = '/data/logs/adminset_agent/'
True if os.path.exists(LOG_DIR) else os.makedirs(LOG_DIR)
log("agent.log", LOG_DIR)


def get_ip():
    try:
        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        s.connect(('8.8.8.8', 80))
        ipaddr = s.getsockname()[0]
    except Exception:
        ipaddr = ''
    finally:
        s.close()
    return ipaddr


def get_dmi():
    p = Popen('dmidecode', stdout=PIPE, shell=True)
    stdout, stderr = p.communicate()
    return stdout.decode('utf-8')


# 格式化dmidecode内容,获取硬件信息
def parser_dmi(dmidata):
    pd = {}
    flag = False
    for line in dmidata.split('\n'):
        if line.startswith('System Information'):
            flag = True
            continue
        # System Information部分信息，碰到System Information字段，flag标记为true，等System Information
        # 往下信息输出完毕，既遇上空格，flag标记为false
        # line.startswith('\t')通过制表符判断System Information往下的参数
        '''
        System Information
            Manufacturer: QEMU
            Product Name: Standard PC (i440FX + PIIX, 1996)
            Version: pc-i440fx-3.0
            Serial Number: Not Specified
            UUID: 2A69C9C4-EA8F-403A-808B-84F15588CFB0
            Wake-up Type: Power Switch
            SKU Number: Not Specified
            Family: Not Specified
            
        '''
        if line.startswith('\t') and flag:
            k, v = [i.strip() for i in line.split(':')]
            pd[k] = v
        else:
            flag = False
    return pd


def get_mem_total():
    cmd = "grep MemTotal /proc/meminfo"
    p = Popen(cmd, stdout=PIPE, shell=True)
    data = p.communicate()[0]
    mem_total = data.split()[1]
    memtotal = int(round(int(mem_total) / 1024.0 / 1024.0, 0))
    return memtotal


def get_cpu_model():
    cmd = "cat /proc/cpuinfo"
    p = Popen(cmd, stdout=PIPE, stderr=PIPE, shell=True)
    stdout, stderr = p.communicate()
    return stdout.decode('utf-8')


def get_cpu_cores():
    cpu_cores = {
        "physical": psutil.cpu_count(logical=False) if psutil.cpu_count(logical=False) else 0,
        "logical": psutil.cpu_count()
    }
    return cpu_cores


def parser_cpu(stdout):
    groups = [i for i in stdout.split('\n\n')]
    cpu_info = {}
    for x in groups[-2].split('\n'):
        k, v = [i.strip() for i in x.split(':')]
        cpu_info[k] = v
    return cpu_info


def get_disk_info():
    ret = []
    cmd = "fdisk -l|egrep '^Disk\s/dev/[a-z]+:\s\w*'"
    p = Popen(cmd, stdout=PIPE, stderr=PIPE, shell=True)
    stdout, stderr = p.communicate()
    for i in stdout.decode('utf-8').split('\n'):
        disk_info = i.split(",")
        if disk_info[0]:
            ret.append(disk_info[0].replace('Disk ', ''))
    return ret


def get_token():
    pass


def post_data(url, data):
    try:
        r = requests.post(url, data=data, headers=headers)
        if r.text:
            logging.info(r.text)
        else:
            logging.info("Server return http status code: {0}".format(r.status_code))
    except Exception as msg:
        logging.info(msg)
    return True


def name_get_code(name):
    cf = ConfigParser.ConfigParser()
    cf.read(filenames='config/nodecode.ini')
    code = cf.get('nodecode', name)
    return code


def pro_get_type(name):
    cf = ConfigParser.ConfigParser()
    cf.read(filenames='config/protype.ini')
    for sec in cf.sections():
        for pro in cf.options(section=sec):
            if name == pro:
                ptype = sec
            else:
                pass
    return ptype


def get_net(ip):
    net = str(re.findall(r'(?<!\d)\d{1,3}\.\d{1,3}\.\d{1,3}(?=\.\d)', ip)[0])
    return net


def qy_belong(ip):
    api = APIConnection(**qy_param)
    describe_dict = api.describe_instances(limit=400, status=['running'])
    for j in describe_dict['instance_set']:
        if ip == j['vxnets'][0]['private_ip']:
            host_machine = j['host_machine']
        else:
            logging.error('agent 获取ip出错: ' + str(ip))
    return host_machine


def pve_belong(ip):
    pass


def belong_to(ip):
    if ip == '172.18.16' or ip == '172.18.32':
        host_machine = qy_belong(ip)
    else:
        host_machine = None
    return host_machine


def get_env_code(ipnet):
    node_code = {'tag': 'smy', 'code': '0'}
    cf = ConfigParser.ConfigParser()
    cf.read(filenames='config/net.ini')
    for net in cf.sections():
        if net.encode('gbk') == ipnet:
            machine_type = cf.get(net, 'machine_type')
            node_code = {'tag': node_code['tag'] + ' ' + machine_type,
                         'code': node_code['code'] + ' ' + name_get_code(machine_type)}
            if machine_type == 'physical':
                node_code = {'tag': node_code['tag'] + ' ' + cf.get(net, 'idc'),
                             'code': node_code['code'] + ' ' + name_get_code(cf.get(net, 'idc'))}
            elif machine_type == 'virtual':
                node_code = {'tag': node_code['tag'] + ' ' + cf.get(net, 'iaas'),
                             'code': node_code['code'] + ' ' + name_get_code(cf.get(net, 'iaas'))}
    return node_code


def get_pro_code():
    node_code = []
    cf = ConfigParser.ConfigParser()
    cf.read(filenames='config/profilter.ini')
    for pro in cf.sections():
        code_dict = {}
        cmd = cf.get(pro, 'cmd')
        ret = os.system(cmd)
        # sub = Popen(cmd, shell=True, stdout=PIPE, stderr=PIPE)
        # sub.stdout.read()
        # ret = sub.poll()
        if ret == 0:
            pro_code = name_get_code(pro)
            pro_type = pro_get_type(pro)
            type_code = name_get_code(pro_type)
            code_dict['tag'] = pro_type + ' ' + pro
            code_dict['code'] = type_code + ' ' + pro_code
            node_code.append(code_dict)
    return node_code


def get_code(ipnet):
    code = []
    for d in get_pro_code():
        pro_dict = {}
        pro_dict['tag'] = get_env_code(ipnet)['tag'] + ' ' + d['tag']
        pro_dict['code'] = get_env_code(ipnet)['code'] + ' ' + d['code']
        code.append(pro_dict)
    return code

# ----------------下面4个是获取pve信息-----------------------#
def pve_login(domain, username, password):
# 获取cookies
# ticket:res['data']['ticket']  任何请求都需要用到ticket
# CSRF防止令牌:res['daSRFPreventionToken']  写请求需要用到CSRF 
    login_url = domain + "api2/json/access/ticket"
    login_data = {"username":username, "password":password}
    headers = {"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:84.0) Gecko/20100101 Firefox/84.0"}
    res = requests.post(login_url, data=login_data, headers=headers).text
    res = json.loads(res)
    return res['data']['ticket'], res['data']['CSRFPreventionToken']

def pve_source(ticket, CSRF, source_type, domain):
# 获取资源信息
    get_source_url = domain + "api2/json/cluster/resources"
    cookies = {'PVEAuthCookie':ticket}
    headers = {'CSRFPreventionToken':CSRF}
    params = {'type':source_type}
    sources = requests.get(get_source_url, cookies=cookies, headers=headers, params=params).text
    sources = json.loads(sources)
    return sources

def pve_node_ip(ticket, CSRF, node, domain):
    cookies = {'PVEAuthCookie':ticket}
    headers = {'CSRFPreventionToken':CSRF}
    get_ip_url = domain + "api2/json/nodes/" + str(node) + "/hosts"
    ret = requests.get(get_ip_url, cookies=cookies, headers=headers)
    # print(ret.text)
    ip = json.loads(ret.text)['data']['data']
    ip = re.search(r"172\.18\.\d{1,3}\.\d{1,3}", ip, re.I|re.M).group()
    return ip

def pve_physical_ip(hostname):
    # domain = "https://pve.smyjf.cn/"
    # username = "root@pam"
    # password = "eG4vue!paA"

    domain1 = "http://172.18.36.1:8006/"
    username1 = "root@pam"
    password1 = "Smy12345"

    domain2 = "http://172.18.82.2:8006/"
    username2 = "root@pam"
    password2 = "pre_siy_pve@20200602!"
    sources_dict = {}

    ticket, CSRF = pve_login(domain1, username1, password1)
    sources = pve_source(ticket,CSRF,'vm', domain1)["data"]
    for i in sources:
        sources_dict[i['name']] = pve_node_ip(ticket,CSRF,i['node'],domain1)

    ticket, CSRF = pve_login(domain2, username2, password2)
    sources = pve_source(ticket,CSRF,'vm', domain2)["data"]
    for i in sources:
        sources_dict[i['name']] = pve_node_ip(ticket,CSRF,i['node'],domain2)
    
    return sources_dict[hostname]

def asset_info():
    data_info = dict()
    cf = ConfigParser.ConfigParser()
    cf.read(filenames='config/net.ini')
    ip = get_ip()
    net = get_net(ip=ip)
    ipnet = net + '.0'
    cpucore = get_cpu_cores()
    for net in cf.sections():
        if net.encode('gbk') == ipnet:
            machine_type = cf.get(net, 'machine_type')
            iaas = cf.get(net, 'iaas')
            if machine_type == 'physical':
                data_info['sn'] = parser_dmi(get_dmi())['Serial Number']
                data_info['cpu_physical'] = cpucore['physical']
            else:
                if iaas == 'pve':
                    data_info['belong_to'] = pve_physical_ip(platform.node())
                else:
                    data_info['belong_to'] = 'test'
    data_info['tag_code'] = get_code(ipnet)
    data_info['memory'] = get_mem_total()
    data_info['disk'] = str(get_disk_info())
    cpuinfo = parser_cpu(get_cpu_model())
    data_info['cpu_cores'] = cpucore['logical']
    data_info['cpu'] = cpuinfo['model name']
    data_info['ip'] = get_ip()
    data_info['vendor'] = parser_dmi(get_dmi())['Manufacturer']
    data_info['product'] = parser_dmi(get_dmi())['Version']
    data_info['hostname'] = platform.node()
    os = distro.linux_distribution()
    data_info['os_type'] = os[0] + ' ' + os[1]
    data_info['os_kernel'] = os[2]
    data_info['release'] = platform.release()
    data_info['instance_id'] = distro.id()
    data_info['instance_type'] = platform.processor()
    data_info['instance_state'] = 'Running'
    data_info['agent_version'] = AGENT_VERSION
    ctime = datetime.now()
    data_info['ctime'] = datetime.strftime(ctime, '%Y-%m-%d %H:%M:%S')
    data_info['next_time'] = datetime.strftime(ctime + timedelta(seconds=60), '%Y-%m-%d %H:%M:%S')
    return json.dumps(data_info)


def asset_info_post():
    # pversion = platform.python_version()
    # pv = re.search(r'2.6', pversion)
    # if not pv:
    #     osenv = os.environ["LANG"]
    #     os.environ["LANG"] = "us_EN.UTF8"
    logging.info('Get the hardwave infos from host:'.encode('utf-8'))
    # logging.info(asset_info().encode('utf-8'))
    logging.info(asset_info())
    post_data("http://{0}/api/cmdb2/v1/cmdb/server/collect/".format(server_ip), asset_info())
    # if not pv:
    #     os.environ["LANG"] = osenv
    return True
# ------------------下面为获取系统资源使用信息--------------------#


def get_sys_cpu():
    sys_cpu = {}
    cpu_time = psutil.cpu_times_percent(interval=1)
    sys_cpu['percent'] = psutil.cpu_percent(interval=1)
    sys_cpu['lcpu_percent'] = psutil.cpu_percent(interval=1, percpu=True)
    sys_cpu['user'] = cpu_time.user
    sys_cpu['nice'] = cpu_time.nice
    sys_cpu['system'] = cpu_time.system
    sys_cpu['idle'] = cpu_time.idle
    sys_cpu['iowait'] = cpu_time.iowait
    sys_cpu['irq'] = cpu_time.irq
    sys_cpu['softirq'] = cpu_time.softirq
    sys_cpu['guest'] = cpu_time.guest
    return sys_cpu


def get_sys_mem():
    sys_mem = {}
    mem = psutil.virtual_memory()
    sys_mem["total"] = mem.total / 1024 / 1024
    sys_mem["percent"] = mem.percent
    sys_mem["available"] = mem.available / 1024 / 1024
    sys_mem["used"] = mem.used / 1024 / 1024
    sys_mem["free"] = mem.free / 1024 / 1024
    sys_mem["buffers"] = mem.buffers / 1024 / 1024
    sys_mem["cached"] = mem.cached / 1024 / 1024
    return sys_mem


def parser_sys_disk(mountpoint):
    partitions_list = {}
    d = psutil.disk_usage(mountpoint)
    partitions_list['mountpoint'] = mountpoint
    partitions_list['total'] = round(d.total / 1024 / 1024 / 1024.0, 2)
    partitions_list['free'] = round(d.free / 1024 / 1024 / 1024.0, 2)
    partitions_list['used'] = round(d.used / 1024 / 1024 / 1024.0, 2)
    partitions_list['percent'] = d.percent
    return partitions_list


def get_sys_disk():
    partition_info = []
    partitions = psutil.disk_partitions()
    for p in partitions:
        partition_info.append(parser_sys_disk(p.mountpoint))
    sys_disk = partition_info
    return sys_disk


def get_nic():
    key_info = [nic for nic in psutil.net_io_counters(pernic=True).keys() if 'eth' in nic]
    recv, sent = {}, {}
    for key in key_info:
        recv.setdefault(key, psutil.net_io_counters(pernic=True).get(key).bytes_recv)
        sent.setdefault(key, psutil.net_io_counters(pernic=True).get(key).bytes_sent)
    return key_info, recv, sent


def get_nic_rate(func):
    key_info, old_recv, old_sent = func()
    time.sleep(1)
    key_info, now_recv, now_sent = func()
    net_in, net_out = {}, {}
    for key in key_info:
        net_in.setdefault(key, (now_recv.get(key) - old_recv.get(key)) / 1024)
        net_out.setdefault(key, (now_sent.get(key) - old_sent.get(key)) / 1024)
    return key_info, net_in, net_out


def get_net_info():
    net_info = []
    key_info, net_in, net_out = get_nic_rate(get_nic)
    for key in key_info:
        in_data, out_data = net_in.get(key), net_out.get(key)
        net_info.append({"nic_name": key, "traffic_in": in_data, "traffic_out": out_data})
    return net_info


def agg_sys_info():
    ctime = datetime.now()
    logging.info('Get the system infos from host:')
    sys_info = {'hostname': platform.node(),
                'ip': get_ip(),
                'cpu': get_sys_cpu(),
                'mem': get_sys_mem(),
                'disk': get_sys_disk(),
                'net': get_net_info(),
                'ctime': datetime.strftime(ctime, '%Y-%m-%d %H:%M:%S')}
    logging.info(sys_info)
    post_data("http://{0}/api/cmdb2/v1/cmdb/server/monitor/sys_info/".format(server_ip), json.dumps(sys_info))
    return True


# ------------------上面为获取系统资源使用信息--------------------#

def run_threaded(job_func):
    job_thread = threading.Thread(target=job_func)
    job_thread.start()


# 防止进程启动多个副本。只有获得pid文件(固定路径固定文件名)写入权限(F_WRLCK)的进程才能正常启动并把自身的PID写入该文件中
# 其它同一个程序的多余进程则自动退出
def get_pid():
    base_dir = os.path.dirname(os.path.abspath('adminsetd.pid'))
    pid = str(os.getpid())
    with open(base_dir + "/adminsetd.pid", "w+") as pid_file:
        pid_file.writelines(pid)


def clean_log():
    os.system("> /data/logs/adminset_agent/agent.log")
    logging.info("clean agent log")


if __name__ == "__main__":
    get_pid()
    
    # 每周一00:20执行清理日志
    schedule.every().monday.at("00:20").do(clean_log)

    # 每 5 分钟执行一次，asset_info_post, agg_sys_info 为 run_threaded参数
    schedule.every(asset_info_interval).seconds.do(run_threaded, asset_info_post)
    schedule.every(agg_sys_interval).seconds.do(run_threaded, agg_sys_info)
    
    while True:
        schedule.run_pending()
        time.sleep(1)
